package com.xu.cloud.common.trans;

import com.xu.cloud.common.aop.ServletRequestPointcut;
import com.xu.cloud.common.aop.XuApiPointcut;
import com.xu.cloud.common.api.ApiContext;
import com.xu.cloud.common.util.*;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.autoproxy.AbstractBeanFactoryAwareAdvisingPostProcessor;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.io.Resource;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;

/**
 * 请求日志aop拦截器
 *
 * @author xuguan
 * @since 2024/9/24
 */
public class TransLogAdvisingBeanPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor {

    public static final Logger log = LoggerFactory.getLogger(TransLogAdvisingBeanPostProcessor.class);

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        super.setBeanFactory(beanFactory);
        final Pointcut requestPc = new ServletRequestPointcut();
        final Pointcut xuApiPc = new XuApiPointcut();
        final ComposablePointcut composablePointcut = new ComposablePointcut(requestPc).union(xuApiPc);
        this.advisor = new DefaultPointcutAdvisor(composablePointcut, new TransContextMethodInterceptor());
    }

    private static class TransContextMethodInterceptor implements MethodInterceptor {
        @Override
        public Object invoke(MethodInvocation methodInvocation) throws Throwable {
            skipStatic(methodInvocation);

            // 交易参数上下文
            final ApiContext apiContext = ApiContext.current();

            // 请求参数
            final Object[] args = methodInvocation.getArguments();

            // 被代理的目标对象
            Object target = AopUtil.getTarget(methodInvocation.getThis());
            String targetClassName = target.getClass().getSimpleName();
            if (target instanceof Class) {
                targetClassName = ((Class<?>) target).getSimpleName();
            }

            // 请求方法
            final Method method = methodInvocation.getMethod();
            final String methodName = method.getName();

            // 请求参数
            final Class<?>[] parameterTypes = method.getParameterTypes();
            final String[] parameterNames = getParameterNames(method);

            // 返回类型
            final Class<?> returnType = method.getReturnType();

            StringBuilder sb = new StringBuilder();

            sb.append(">>>>>开始执行: ")
                    .append(targetClassName)
                    .append("#")
                    .append(methodName);
            sb.append("(");
            for (int i = 0; i < parameterTypes.length; i++) {
                sb.append(parameterTypes[i].getSimpleName())
                        .append(" ")
                        .append(parameterNames[i]);
                if (i < parameterNames.length - 1) {
                    sb.append(", ");
                }
            }
            sb.append(")").append("方法");

            log.info(sb.toString());
            ServletLogUtil.reqJournalTraceLog();

            for (int i = 0; i < parameterNames.length; i++) {
                log.info("请求参数arg{}: {}={}", i, parameterNames[i], getValue(args[i]));
            }

            // 请求执行
            final Object returnObj;
            try {
                returnObj = methodInvocation.proceed();

                // 响应日志
                log.info(">>>>>执行完成, 返回实体类{}: {}", returnType.getSimpleName(), getValue(returnObj));
            } catch (Throwable throwable) {

                // 异常日志, 只打印异常类型, 异常堆栈信息交给ExceptionHandler打印
                log.error(">>>>>执行异常, 异常类型: {}", ExceptionUtil.unwrapThrowable(throwable).getClass().getSimpleName());
                throw throwable;
            }

            return returnObj;
        }

        private String[] getParameterNames(Method method) {
            // 参数名称发现
            final ParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
            final String[] parameterNames = discoverer.getParameterNames(method);
            if (Objects.nonNull(parameterNames)) {
                return parameterNames;
            }
            String[] argNames = new String[method.getParameterCount()];
            for (int i = 0; i < argNames.length; i++) {
                argNames[i] = "arg" + i;
            }
            return argNames;
        }

        private String getValue(@Nullable final Object arg) {
            String value;
            if (Objects.isNull(arg)) {
                value = "[null]";
            } else if (isFilterObj(arg)) {
                value = "[ignore]";
            } else {
                value = JsonUtil.toJsonString(arg);
            }
            return value;
        }

        private boolean isFilterObj(@NonNull final Object o) {
            final Class<?> cls = o.getClass();

            if (cls.isArray()) {
                for (int i = 0; i < Array.getLength(o); i++) {
                    if (isFilterObj(Array.get(o, i))) {
                        return true;
                    }
                }
            }

            if (Collection.class.isAssignableFrom(cls)) {
                return ((Collection<?>) o).stream()
                        .anyMatch(this::isFilterObj);
            }

            if (Map.class.isAssignableFrom(cls)) {
                return ((Map<?, ?>) o).entrySet().stream()
                        .anyMatch(this::isFilterObj);
            }

            return o instanceof HttpServletRequest
                    || o instanceof HttpServletResponse
                    || o instanceof MultipartFile
                    || o instanceof BindResult
                    || o instanceof InputStream
                    || o instanceof OutputStream
                    || o instanceof Resource;
        }

        private void skipStatic(MethodInvocation methodInvocation) throws Throwable {
            final String requestUri = HttpServletUtil.currentRequest().getRequestURI();
            if (ResourceUtil.isStatic(requestUri)) {
                methodInvocation.proceed();
            }
        }
    }
}
